/*******************************************************************************
 * Copyright (c) 2003 Boris Pruessmann and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Common Public License v0.5 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/cpl-v05.html
 *
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.api;

import com.google.common.collect.Sets;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @version $Revision: 489 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class FileType implements IFileType {
  // --------------------------------------------------------------------
  // Private Static Variables

  /** Holds the mapping for backward compatible filetypes. */
  private static final Map<String, String> backwardCompatibilityMap = new HashMap<String, String>();
  /** Holds the mapping for forward compatible filetypes. */
  private static final Map<String, String> forwardCompatibilityMap = new HashMap<String, String>();
  /** Holds the single ModifierComparator instance. */
  private static final ModifierComparator MODIFIER_COMPARATOR = new ModifierComparator();

  // -------------------------------------------------------------------- Static
  // Initializer Block

  static {
    backwardCompatibilityMap.put("ctext", "text+C"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("cxtext", "text+Cx"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("ktext", "text+k"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("kxtext", "text+kx"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("ltext", "text+F"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("tempobj", "binary+Sw"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("ubinary", "binary+F"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("uresource", "resource+F"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("uxbinary", "binary+Fx"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("xbinary", "binary+x"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("xltext", "text+Fx"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("xtempobj", "binary+Swx"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("xtext", "text+x"); //$NON-NLS-1$//$NON-NLS-2$
    backwardCompatibilityMap.put("xunicode", "unicode+x"); //$NON-NLS-1$//$NON-NLS-2$

    forwardCompatibilityMap.put("text+C", "ctext"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("text+Cx", "cxtext"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("text+k", "ktext"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("text+kx", "kxtext"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("text+F", "ltext"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("binary+Sw", "tempobj"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("binary+F", "ubinary"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("resource+F", "uresource"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("binary+Fx", "uxbinary"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("binary+x", "xbinary"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("text+Fx", "xltext"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("binary+Swx", "xtempobj"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("text+x", "xtext"); //$NON-NLS-1$//$NON-NLS-2$
    forwardCompatibilityMap.put("unicode+x", "xunicode"); //$NON-NLS-1$//$NON-NLS-2$
  }

  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the base file type. */
  private BaseFileTypeEnum baseFileTypeEnum;
  /**
   * Stores the file type modifiers (FileTypeModifierEnum,
   * ServerStorageMethodEnum)
   */
  private Set<Object> fileTypeModifierSet = Sets.newTreeSet(MODIFIER_COMPARATOR);

  // -------------------------------------------------------------------------
  // Public Constructors

  /**
   * Constructor for FileType.
   *
   * @param fileType the filetype string.
   */
  public FileType(String fileType) {
    parseFileTypeString(fileType);
  }

  /**
   * Constructor for FileType.
   *
   * @param theBaseFileTypeEnum file base file type.
   * @param theFileTypeModifierSet set conatining file type modifiers.
   */
  public FileType(BaseFileTypeEnum theBaseFileTypeEnum, Set<?> theFileTypeModifierSet) {
    baseFileTypeEnum = theBaseFileTypeEnum;
    fileTypeModifierSet.addAll(theFileTypeModifierSet);
    fileTypeModifierSet.remove(ServerStorageMethodEnum.DEFAULT);
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  public BaseFileTypeEnum getBaseFileType() {
    return baseFileTypeEnum;
  }

  public Set<Object> getModifierSet() {
    return Collections.unmodifiableSet(fileTypeModifierSet);
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder(baseFileTypeEnum.toString());
    if (!fileTypeModifierSet.isEmpty()) {
      result.append('+');
      for (Object o : fileTypeModifierSet) {
        result.append(o);
      }
    }

    final String resultString = result.toString();
    String tempResultString = forwardCompatibilityMap.get(resultString);
    if (tempResultString != null) {
      return tempResultString;
    }

    return resultString;
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /**
   * Parses the given file type string, setting the values of this object.
   *
   * @param fileType the file type as string.
   */
  private void parseFileTypeString(String fileType) {
    final String tempValue = backwardCompatibilityMap.get(fileType);
    if (tempValue != null) {
      fileType = tempValue;
    }

    int pos = fileType.indexOf('+');
    pos = (pos != -1) ? pos : fileType.length();

    baseFileTypeEnum = BaseFileTypeEnum.fromDescription(fileType.substring(0, pos));
    fileType = fileType.substring(pos);

    final char[] modifiers = fileType.toCharArray();
    for (int i = 0; i < modifiers.length; i++) {
      final char modifierCharacter = modifiers[i];
      switch (modifierCharacter) {
        case '+':
          break;

        case 'm':
          fileTypeModifierSet.add(FileTypeModifierEnum.MODTIME);
          break;

        case 'w':
          fileTypeModifierSet.add(FileTypeModifierEnum.WRITEABLE);
          break;

        case 'x':
          fileTypeModifierSet.add(FileTypeModifierEnum.EXEC);
          break;

        case 'k':
          fileTypeModifierSet.add(FileTypeModifierEnum.KEYWORD);
          break;

        case 'o':
          fileTypeModifierSet.add(FileTypeModifierEnum.KEYWORD_ONLY_ID_HEADER);
          break;

        case 'l':
          fileTypeModifierSet.add(FileTypeModifierEnum.LOCKED);
          break;

        case 'C':
          fileTypeModifierSet.add(ServerStorageMethodEnum.COMPRESSED);
          break;

        case 'D':
          fileTypeModifierSet.add(ServerStorageMethodEnum.DELTA);
          break;

        case 'F':
          fileTypeModifierSet.add(ServerStorageMethodEnum.FULL);
          break;

        case 'S':
          fileTypeModifierSet.add(ServerStorageMethodEnum.SINGLE);
          break;
      }
    }
  }

  // ----------------------------------------------------------------------
  // Private Nested Classes

  /** Compares objects based on their string representation. */
  private static class ModifierComparator implements Comparator<Object> {
    public int compare(Object o1, Object o2) {
      return o1.toString().compareTo(o2.toString());
    }
  }
}
